深入理解java虚拟机 第十二章 Java内存模型与线程
java内存模型
主内存与工作内存
java内存模型规定了所有的变量都在主内存中,每条线程还有自己的工作内存
工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行
内存间交互操作
虚拟机必须保证以下8种对内存的操作必须是原子的:
1 lock:作用于主内存的变量,它把一个变量标识为一条线程独占的状态
2 unlock:作用于主内存的变量,把锁定的变量释放出来
3 read:作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中,
4 load:作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中
5 use:作用于工作内存的变量,把工作内存中一个变量的值传递给执行引擎
6 assign:作用于工作内存的变量,把一个从执行引擎接收到的值赋给工作内存的变量
7 store:作用于工作内存的变量,把工作内存中一个变量的值传送到主内存
8 write:作用于主内存的变量,把store操作从工作内存中得到的变量的值放入主内存中
对于volatile型变量的特殊规则
当一个变量定义为volatile后,它将具备两种特性:
1 保证此变量对所有线程的可见性,
但是volatile变量的运算在并发下一样是不安全的,所以只能保证可见性
2 禁止指令重排序优化
大多数场景下,volatile的总开销比锁低,但是虚拟机对锁实行的许多消除和优化,使得我们很难量化地认为volatile
就比synchronized快多少,不同的选择还要看场景的需求
原子性、可见性与有序性
先行发生原则:
先行发生是java内存中定义的两项操作之间的偏序关系,如果给说操作a先行发生于操作b
其实是说发生操作b之前,操作a产生的影响能被操作b观察到
java虚拟机中有一些"天然的"先行发生关系,无须任何同步器协助就已经存在,可以直接使用
如果不在此列,就没有顺序保证,虚拟机可以对它们随意进行重排序
1 程序次序规则:在一个线程内,按照程序代码顺序,写在前面的操作先行发生于写在后面的操作
2 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作
3 volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作
4 线程启动规则:thread对象的start()方法先行发生于此线程的每一个动作
5 线程终止规则:线程中所有操作都先行发生于此线程的终止检测
6 线程中断规则:
7 对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始
8 传递性:如果操作a先行发生于操作b,操作b先行发生于操作c,那么操作a先行发生于操作c
java与线程
线程的实现
在java API中,一个Native方法往往意味着这个方法没有使用或无法使用平台无关的手段来实现
1 使用内核线程实现
内核线程是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,
并负责将线程的任务映射到各个处理器上,支持多线程的内核就叫做多线程内核
程序一般不会直接去使用内核线程,而去使用内核线程的一个高级接口---轻量级进程
轻量级进程就是我们所讲的线程,每个轻量级进程都是由一个内核线程支持
这种轻量级进程与内核线程之间1:1的关系称为一对一的线程模型
2 使用用户线程实现
狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现
用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助
这种进程和用户线程之间1:n的关系称为一对多的线程模型
使用用户线程实现的程序一般都比较复杂,java曾经使用用户线程,又都放弃使用它
3 使用用户线程加轻量级进程混合实现
在这种混合模式中,用户线程和轻量级进程的数量比是不定的,N:M
4 java线程的实现
在JDK1.2中,线程模型替换为基于操作系统原生线程模型来实现
在目前JDK中,操作系统支持怎样的线程模型,在很大程度上决定了java虚拟机的线程是怎样映射的
java线程调度
1 协同式线程调度
线程的执行时间由线程本身来控制,线程把自己的工作执行完成后,主动通知系统切换到另一个线程上
线程执行时间不可控制,会阻塞
2 抢占式调度
每个线程将由系统来分配时间,
状态转换
java语言定义了5种线程状态,在任意时间点,一个线程只能有且只有其中的一种状态
1 新建:创建后尚未启动的线程处于这种状态
2 运行:可能正在执行,也可能正在等待CPU为它分配执行时间
3 无期限等待:不会被分配CPU时间,要等待被其他线程显式地唤醒,以下方法会让线程陷入无期限的等待状态
* 没有设置Timeout参数的Object.wait()方法
* 没有设置Timeout参数的Thread.join()方法
* LockSupport.park()方法
4 期限等待:这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒
在一定时间后,它们会由系统自动唤醒
5 阻塞:在等待获取一个排他锁,这个事件将在另外一个线程放弃这个锁的时候发生
6 结束